Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for maximum expiration of a JWT token #3331

Closed
wants to merge 1 commit into from

Conversation

mvanholsteijn
Copy link
Contributor

@mvanholsteijn mvanholsteijn commented Mar 22, 2018

Summary

Support for limiting the expiration period on JWT tokens.

In the JWT plugin you can set the property 'maximum_expiration' to a positive integer, indicating the maximum number of seconds the 'exp' claim in the token may be ahead in the future.

The purpose of this feature is to prevent the use of tokens with extreme long lifetimes.

Full changelog

  • Added the 'maximum_expiration' to the jwt plugin schema
  • Added a check in the handler
  • Added unit tests for handler and schema.

@mvanholsteijn mvanholsteijn changed the title added support for maximum duration added support for maximum expiration of a JWT token Mar 22, 2018
@jeremyjpj0916
Copy link
Contributor

jeremyjpj0916 commented Mar 26, 2018

I like the idea here, but not all JWT's will include the exp and nbf, why not also make an if validating on if (exp - ngx.time()) or w/e you have if you want to keep token expiry an hour or less? It's something we implemented too to prevent clients from being irresponsible with their tokens.

@mvanholsteijn
Copy link
Contributor Author

I thought it would be cheaper to do the (exp -nbf) calculation. In addition, with (exp-nbf) I did not have to deal with clock skew.

I will change it, to use exp only. leaving the configuration of maximum_expiration to deal with possible clock skew..

Cheers,

Mark

@mvanholsteijn
Copy link
Contributor Author

@jeremyjpj0916 can you please review my pull request? Once it is integrated, I can stop merging the changes from master into this one 😃

@jeremyjpj0916
Copy link
Contributor

@mvanholsteijn I don't have write access, I chimed in only because I was implementing a similar change personally and thought to bring up the scenario where just exp is exposed and to compare against ngx.time() , which is looks like you added that so overall I think this is a nice feature! Hopefully kong team agrees and will consider implementing it as it makes sense from a security perspective.

@mvanholsteijn
Copy link
Contributor Author

@jeremyjpj0916 I did not know you did not have write access :-)

We implemented this feature after an external security audit was performed on our API. They recommended to limit the lifetime of the jwt token.

@mvanholsteijn
Copy link
Contributor Author

@thibaultcha Do you have any idea on when this PR might be reviewed?

Copy link
Member

@thibaultcha thibaultcha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mvanholsteijn Thank you for the patch! And sorry for the delay.

Will you apply the recommended changes? Also, since this patch contains migrations, it must be opened against next, and not master. Please do not close this PR and open a new one, instead, use the GitHub UI to change the base of your PR (it might need rebasing as well).
Since the next release version is 0.14.0 anyway, there it should not delay this from being released.

local payload = {
iss = jwt_secret.key,
exp = os.time() + 3600,
nbf = os.time() - 30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: indentation is off here and in several other places in this file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

end
end
if not has_exp then
return false, Errors.schema "when you specify maximum_expiration, 'exp' must be in claims_to_verify"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: indentation is off here and above in the same function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to come up with a more standard/consistent error message:

"claims_to_verify must contain 'exp' when specifying maximum_expiration"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to "To set maximum_expiration, you need to add 'exp' in claims_to_verify"

path = "/request/?jwt=" .. jwt,
headers = {
["Host"] = "jwt11.com"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

@@ -18,5 +26,20 @@ return {
claims_to_verify = {type = "array", enum = {"exp", "nbf"}},
anonymous = {type = "string", default = "", func = check_user},
run_on_preflight = {type = "boolean", default = true},
maximum_expiration = {type = "number", default = -1, func = check_maximum_expiration_positive},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a more common pattern is to treat 0 as the "indefinite expiration", and any value below zero as invalid (as if it were an unsigned integer). The semantics are somewhat more obvious (-1 has a strong "error-related" connotation and also feels strange in a time-related value).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right. fixed.

@@ -165,6 +165,15 @@ local function do_authentication(conf)
return false, {status = 401, message = errors}
end

-- Verify the JWT registered claims
if conf.maximum_expiration ~= nil and conf.maximum_expiration > 0 then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second check of this condition seems unnecessary, since you included a safeguard to check_maximum_expiration() already (if max_exp <= 0)

Copy link
Contributor Author

@mvanholsteijn mvanholsteijn May 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to avoid calling the function as I know it is not necessary saving a few CPU cycles.

end
else
return false, {exp = "is missing"}
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about: "exceeds maximum allowed expiration"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, as per our contributing guide, we recommend hoisting short branches with early exits for clarity, and a few other minor nitpicks:

local exp = self.claims.exp
if not exp then
  return false, { exp = "missing" }
end

if exp - ngx_time() > maximum_expiration then
  return false, { exp = "exceeds maximum allowed expiration" }
end

return true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to the message "exceeds maximum allowed expiration" if the 'exp' is missing.

@@ -8,6 +9,13 @@ local function check_user(anonymous)
return false, "the anonymous user must be empty or a valid uuid"
end

local function check_maximum_expiration_positive(v)
if v < -1 or v == 0 then
return false, "maximum_expiration should be -1 or greater than 0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to repeat the name of the property in this error message, it will already be contained in the key for this error. We are also lacking a test for this validation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

local has_exp = false
if plugin_t.claims_to_verify then
for index, value in ipairs(plugin_t.claims_to_verify) do
has_exp = has_exp or value == "exp"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more convenient pattern is to break out of the loop:

if value == "exp" then
  has_exp = true
  break
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@thibaultcha thibaultcha added pr/changes requested Changes were requested to this PR by a maintainer. Please address them and ping back once done. and removed pr/please review labels May 19, 2018
@mvanholsteijn mvanholsteijn changed the base branch from master to next May 20, 2018 08:30
@mvanholsteijn
Copy link
Contributor Author

@thibaultcha I processed all (but 1) remarks.

Copy link
Member

@thibaultcha thibaultcha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @mvanholsteijn

I think this is almost ready. I was about to merge your patch manually with the modifications I just mentioned, but since there is a merge commit in your PR, it made things more difficult and I'd rather ask you to either properly rebase your branch on top of next, or made those modifications yourself (in which case I will squash + merge your changes). A proper rebase would be much appreciated as it makes our life a lot easier. For reference see the CONTRIBUTING.md guidelines which recommend following this process when contributing to this repo.

Thank you!

end
end
if not has_exp then
return false, Errors.schema "To set maximum_expiration, you need to add 'exp' in claims_to_verify"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A convention followed by absolutely all error messages in Kong is to not capitalize the sentences. Better not deviate from it (hence my previous suggestion).

@@ -8,6 +9,13 @@ local function check_user(anonymous)
return false, "the anonymous user must be empty or a valid uuid"
end

local function check_positive(v)
if v < 0 then
return false, "should be >= 0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another convention our error messages follow is to use words instead of signs. In this case:

should be 0 or greater
or:
cannot be negative


local exp = self.claims["exp"]
if exp == nil or (exp - ngx_time()) > maximum_expiration then
return false, {exp = "exceeds maximum allowed expiration"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: the indentation in this codebase is 2 spaces (this one uses 4 and the above branch uses 3)

-- @return error if any
function _M:check_maximum_expiration(maximum_expiration)
if maximum_expiration <= 0 then
return true, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return true would be enough here as per our return conventions.

if plugin_t.maximum_expiration ~= nil and plugin_t.maximum_expiration > 0 then
local has_exp = false
if plugin_t.claims_to_verify then
for index, value in ipairs(plugin_t.claims_to_verify) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: indentation is weird here as well (1 space?)

@mvanholsteijn
Copy link
Contributor Author

Fixed latest review comments and rebased on upstream/next.

PS: Is there a way to get luacheck to complain about indentation?

thibaultcha pushed a commit that referenced this pull request May 23, 2018
Support for limiting the expiration period on JWT tokens.

In the JWT plugin you can set the property `maximum_expiration` to a
positive integer, indicating the maximum number of seconds the `exp`
claim in the token may be ahead in the future.

From #3331

Signed-off-by: Thibault Charbonnier <[email protected]>
@thibaultcha
Copy link
Member

Merged with some modifications (testing, formatting, errors), thanks again for your contribution

@mvanholsteijn
Copy link
Contributor Author

@thibaultcha thank you for your support!

@mvanholsteijn mvanholsteijn deleted the maximum_expiration branch May 23, 2018 10:06
thibaultcha pushed a commit to Kong/docs.konghq.com that referenced this pull request Jun 28, 2018
ukiahsmith pushed a commit to Kong/docs.konghq.com that referenced this pull request Jul 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr/changes requested Changes were requested to this PR by a maintainer. Please address them and ping back once done.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants